package org.apache.lucene.index;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.lucene.index.TagIndexSnapshot.TagMultiTermEnum;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.BitVector;

public class TagTermDocs implements TermPositions {
  private TagIndexSnapshot snapshot;
  private TagBlockTermDocs current;
  private TagBlockTermDocs[] termDocsArray;
  private TagBlockInfos blockInfos;
  private final int[] starts;
  int pointer;
  int base;
  private final BitVector deletedDocs;
  IndexInput indexInput;
  IndexInput tlogInput;
  int skipInterval;
  int maxSkipLevels;
  Term term;

  public TagTermDocs(TagIndexSnapshot snapshot, int[] starts, BitVector deletedDocs, IndexInput indexInput, IndexInput tlogInput, int skipInterval, int maxSkipLevels) {
    this.snapshot = snapshot;
    this.starts = starts;
    this.deletedDocs = deletedDocs;
    this.indexInput = (IndexInput) indexInput.clone();
    this.tlogInput = (IndexInput) tlogInput.clone();
    this.skipInterval = skipInterval;
    this.maxSkipLevels = maxSkipLevels;
  }
  
  public boolean isPayloadAvailable() {
    return false;
  }
  
  public byte[] getPayload(byte[] data, int offset) throws IOException {
    throw new UnsupportedOperationException("This reader does not support this method.");
  }
  
  public int getPayloadLength() {
    throw new UnsupportedOperationException("This reader does not support this method.");
  }
  
  public int nextPosition() throws IOException {
    throw new UnsupportedOperationException("This reader does not support this method.");
  }
  
  public void seek(TermEnum termEnum) throws IOException {
    TagMultiTermEnum tagMultiTermEnum = (TagMultiTermEnum) termEnum;
    TagTermInfo tagTermInfo = tagMultiTermEnum.termInfo();
    seek(tagTermInfo, termEnum.term());
  }

  public void seek(Term term) throws IOException {
    TagTermInfo tti = snapshot.getTagTermInfo(term);
    seek(tti, term);
  }

  public int freq() {
    return 1;
  }

  public void close() throws IOException {
    indexInput.close();
    tlogInput.close();
  }

  void seek(TagTermInfo ti, Term term) throws IOException {
    this.term = term;
    this.base = 0;
    this.pointer = 0;
    this.current = null;
    // load blockinfos
    blockInfos = snapshot.getTagBlockInfos(term);
    /**
    TagBlockInfo[] indexBlockInfos = loadIndexBlockInfos(ti.blockInfosPointer);
    Set blockNumSet = new HashSet(tlogBlockInfos.length + indexBlockInfos.length);
    for (int x=0; x < tlogBlockInfos.length; x++) {
      TagBlockInfo tagBlockInfo = tlogBlockInfos[x];
      blockNumSet.add(tagBlockInfo.blockNum);
    }
    List goodIndexBlockInfos = new ArrayList(indexBlockInfos.length + tlogBlockInfos.length);
    for (int x=0; x < indexBlockInfos.length; x++) {
      TagBlockInfo tagBlockInfo = indexBlockInfos[x];
      if (!blockNumSet.contains(tagBlockInfo.blockNum)) blockNumSet.add(tagBlockInfo.blockNum);
    }
    blockInfos = new TagBlockInfo[goodIndexBlockInfos.size()];
    for (int x=0; x < blockInfos.length; x++) {
      blockInfos[x] = (TagBlockInfo)goodIndexBlockInfos.get(x);
    }
    blockInfos = null;
    **/
  }
  /**
  private TagBlockInfo[] loadIndexBlockInfos(long blockInfosPointer) throws IOException {
    indexInput.seek(blockInfosPointer);
    int num = indexInput.readVInt();
    TagBlockInfo[] array = new TagBlockInfo[num];
    for (int x = 0; x < num; x++) {
      TagBlockInfo tagBlockInfo = new TagBlockInfo();
      // TODO: should be able to calculate docstart and maxdocs from blocknum
      tagBlockInfo.docStart = indexInput.readVInt();
      tagBlockInfo.filePointer = indexInput.readVLong();
      tagBlockInfo.skipOffset = indexInput.readVInt();
      tagBlockInfo.numDocs = indexInput.readVInt();
      tagBlockInfo.blockNum = indexInput.readVInt();
      tagBlockInfo.maxDoc = indexInput.readVInt();
      tagBlockInfo.tlog = false;
      array[x] = tagBlockInfo;
    }
    return array;
  }
  **/
  public int doc() {
    return base + current.doc();
  }
  
  public boolean next() throws IOException {
    for (;;) {
      if (current != null && current.next()) {
        return true;
      } else if (pointer < termDocsArray.length) {
        base = starts[pointer];
        current = termDocs(pointer++);
      } else {
        return false;
      }
    }
  }

  private TagBlockTermDocs termDocs(int i) throws IOException {
    if (termDocsArray[i] == null) {
      IndexInput stream = null;
      if (blockInfos.tlogs[i]) {
        stream = tlogInput;
      } else {
        stream = indexInput;
      }
      termDocsArray[i] = new TagBlockTermDocs(i, blockInfos, deletedDocs, stream, skipInterval, maxSkipLevels);
    }
    return termDocsArray[i];
  }

  public int read(final int[] docs, final int[] freqs) throws IOException {
    while (true) {
      while (current == null) {
        if (pointer < termDocsArray.length) { 
          base = starts[pointer];
          current = termDocs(pointer++);
        } else {
          return 0;
        }
      }
      int end = current.read(docs, freqs);
      if (end == 0) {
        current = null;
      } else { 
        final int b = base; 
        for (int i = 0; i < end; i++)
          docs[i] += b;
        return end;
      }
    }
  }
  
  public boolean skipTo(int target) throws IOException {
    for (;;) {
      if (current != null && current.skipTo(target - base)) {
        return true;
      } else if (pointer < termDocsArray.length) {
        //pointer = blockIndex(target, starts, blockInfos.length, pointer);
        pointer = binarySearch(starts, target, pointer, termDocsArray.length - pointer);
        base = starts[pointer];
        current = termDocs(pointer);
      } else
        return false;
    }
  }
  
  // from instantiatedindex
  public static final int binarySearch(int[] array, int key, int offset, int length) {//min, int max) {
    if (length == 0) {
      return -1 - offset;
    }
    int min = offset, max = offset + length - 1;
    int minVal = array[min], maxVal = array[max];
    
    int nPreviousSteps = 0;
    
    // Uncomment these two lines to get statistics about the average number of steps in the test report :
    //totalCalls++;
    for (;;) {
      //totalSteps++;
      
      // be careful not to compute key - minVal, for there might be an integer overflow.
      if (key <= minVal) return key == minVal ? min : -1 - min;
      if (key >= maxVal) return key == maxVal ? max : -2 - max;
      
      assert min != max;
      
      int pivot;
      // A typical binarySearch algorithm uses pivot = (min + max) / 2.
      // The pivot we use here tries to be smarter and to choose a pivot close to the expectable location of the key.
      // This reduces dramatically the number of steps needed to get to the key.
      // However, it does not work well with a logaritmic distribution of values, for instance.
      // When the key is not found quickly the smart way, we switch to the standard pivot.
      if (nPreviousSteps > 2) {
        pivot = (min + max) >> 1;
        // stop increasing nPreviousSteps from now on
      } else {
        // NOTE: We cannot do the following operations in int precision, because there might be overflows.
        //       long operations are slower than float operations with the hardware this was tested on (intel core duo 2, JVM 1.6.0).
        //       Overall, using float proved to be the safest and fastest approach.
        pivot = min + (int)((key - (float)minVal) / (maxVal - (float)minVal) * (max - min));
        nPreviousSteps++;
      }
      
      int pivotVal = array[pivot];
      
      // NOTE: do not store key - pivotVal because of overflows
      if (key > pivotVal) {
        min = pivot + 1;
        max--;
      } else if (key == pivotVal) {
        return pivot;
      } else {
        min++;
        max = pivot - 1;
      }
      maxVal = array[max];
      minVal = array[min];
    }
  }
  
  static int blockIndex(int n, int[] starts, int numBlocks, int lo) {    // find reader for doc n:
    int hi = starts.length - 1;                  // for first element less
    while (hi >= lo) {
      int mid = (lo + hi) >> 1;
      int midValue = starts[mid];
      if (n < midValue)
        hi = mid - 1;
      else if (n > midValue)
        lo = mid + 1;
      else {                                      // found a match
        while (mid+1 < numBlocks && starts[mid+1] == midValue) {
          mid++;                                  // scan to last match
        }
        return mid;
      }
    }
    return hi;
  }
}
